package cn.walls1717.wallsmall.order.service.impl;

import cn.walls1717.wallsmall.common.exception.NoStockException;
import cn.walls1717.wallsmall.common.pojo.to.OrderTO;
import cn.walls1717.wallsmall.common.pojo.to.mq.SeckillOrderTO;
import cn.walls1717.wallsmall.common.pojo.vo.MemberRespVO;
import cn.walls1717.wallsmall.common.utils.PageUtils;
import cn.walls1717.wallsmall.common.utils.Query;
import cn.walls1717.wallsmall.common.utils.R;
import cn.walls1717.wallsmall.order.dao.OrderDao;
import cn.walls1717.wallsmall.order.entity.OrderEntity;
import cn.walls1717.wallsmall.order.entity.OrderItemEntity;
import cn.walls1717.wallsmall.order.entity.PaymentInfoEntity;
import cn.walls1717.wallsmall.order.feign.CartFeignService;
import cn.walls1717.wallsmall.order.feign.MemberFeignService;
import cn.walls1717.wallsmall.order.feign.ProductFeignService;
import cn.walls1717.wallsmall.order.feign.WareFeignService;
import cn.walls1717.wallsmall.order.pojo.po.MemberReceiveAddressDO;
import cn.walls1717.wallsmall.order.pojo.to.OrderCreateTO;
import cn.walls1717.wallsmall.order.pojo.vo.*;
import cn.walls1717.wallsmall.order.service.OrderItemService;
import cn.walls1717.wallsmall.order.service.OrderService;
import cn.walls1717.wallsmall.order.service.PaymentInfoService;
import cn.walls1717.wallsmall.order.util.UserHandler;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static cn.walls1717.wallsmall.common.constant.order.OrderRabbitConstant.*;
import static cn.walls1717.wallsmall.common.constant.order.OrderStatusEnum.*;
import static cn.walls1717.wallsmall.order.constant.OrderConstant.USER_ORDER_TOKEN_PREFIX;

@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
    private final ThreadLocal<OrderSubmitVO> orderSubmitThreadLocal = new ThreadLocal<>();
    @Resource
    private MemberFeignService memberFeignService;
    @Resource
    private CartFeignService cartFeignService;
    @Resource
    private ThreadPoolExecutor executor;
    @Resource
    private WareFeignService wareFeignService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ProductFeignService productFeignService;
    @Resource
    private OrderItemService orderItemService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private PaymentInfoService paymentInfoService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(new Query<OrderEntity>().getPage(params), new QueryWrapper<>());
        return new PageUtils(page);
    }

    /**
     * 获取订单确认信息
     *
     * @return 订单确认信息
     */
    @Override
    public OrderConfirmVO getOrderConfirm() throws ExecutionException, InterruptedException {
        OrderConfirmVO orderConfirm = new OrderConfirmVO();
        MemberRespVO loginUser = UserHandler.get();
        // 拿到当前线程的请求属性
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            /*
             * 因为是异步调用，这里会开一个新线程，新线程中并没有请求属性。
             * 此时将这个异步方法外已经获取到的请求属性设置到此线程中即可。
             * RequestContextHolder底层也是通过ThreadLocal实现的。
             */
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberReceiveAddressDO> addresses = memberFeignService.listAddressByMemberId(loginUser.getId());
            orderConfirm.setAddress(addresses);
        }, executor);

        // 设置items与库存信息
        CompletableFuture<Void> itemsFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVO> checkItems = cartFeignService.listCurrentUserCartItems();
            orderConfirm.setItems(checkItems);
        }, executor).thenRunAsync(() -> {
            // 设置过items后执行，获取items。
            List<OrderItemVO> items = orderConfirm.getItems();
            List<Long> skuIds = items.stream().map(OrderItemVO::getSkuId).collect(Collectors.toList());
            // 设置item的库存状态
            List<SkuHasStockVO> skuHasStock = wareFeignService.listSkuWaresBySkuIds(skuIds);
            if (skuHasStock != null) {
                Map<Long, Boolean> stockMap =
                    skuHasStock.stream().collect(Collectors.toMap(SkuHasStockVO::getSkuId, SkuHasStockVO::getHasStock));
                orderConfirm.setStocks(stockMap);
            }
        }, executor);

        orderConfirm.setIntegration(loginUser.getIntegration());

        // 防止订单重复提交
        String token = UUID.randomUUID().toString().replace("-", "");
        stringRedisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX + loginUser.getId(), token, 30, TimeUnit.MINUTES);
        orderConfirm.setOrderToken(token);

        CompletableFuture.allOf(addressFuture, itemsFuture).get();

        return orderConfirm;
    }

    /**
     * 下单
     *
     * @param orderSubmitVO 订单数据
     * @return 下单成功响应数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderSubmitRespVO submitOrder(OrderSubmitVO orderSubmitVO) {
        OrderSubmitRespVO orderResp = new OrderSubmitRespVO();
        // 默认先将状态置位0，成功状态。
        orderResp.setCode(0);
        // 获取登录用户信息
        MemberRespVO loginUser = UserHandler.get();
        // 获取前端提交过来的token，用来做订单防重。
        String token = orderSubmitVO.getOrderToken();

        // 前端没有传入token，直接返回错误状态码。
        if (token == null) {
            // 错误码1，token错误。
            orderResp.setCode(1);
            return orderResp;
        }

        // 使用lua脚本，保证token的比对与删除是原子操作。
        String script =
            "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String tokenKey = USER_ORDER_TOKEN_PREFIX + loginUser.getId();
        Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(tokenKey), token);
        assert result != null;
        // [1表示token比对成功并成功删除，0表示比对失败或删除失败]
        if (result != 1L) {
            // token比对或删除失败，直接返回错误状态码。
            orderResp.setCode(1);
            return orderResp;
        }

        // 在ThreadLocal设置数据，在线程中共享数据。
        orderSubmitThreadLocal.set(orderSubmitVO);
        OrderCreateTO orderCreateTO = buildOrderCreateTO();

        // 验价，比对前端传入的订单总额与最新的订单总额是否相同。
        BigDecimal payAmount = orderCreateTO.getOrder().getPayAmount();
        BigDecimal payPrice = orderSubmitVO.getPayPrice();
        if (Math.abs(payAmount.subtract(payPrice).doubleValue()) > 0.01) {
            // 错误状态码2，验价失败。
            orderResp.setCode(2);
            return orderResp;
        }

        // 保存订单与订单项
        saveOrder(orderCreateTO);

        // 锁定库存
        WareSkuLockVO wareSkuLockVO = new WareSkuLockVO();
        wareSkuLockVO.setOrderSn(orderCreateTO.getOrder().getOrderSn());
        List<OrderItemVO> locks = orderCreateTO.getItems().stream().map(item -> {
            OrderItemVO orderItemVO = new OrderItemVO();
            orderItemVO.setSkuId(item.getSkuId());
            // 购买数量
            orderItemVO.setCount(item.getSkuQuantity());
            // 商品标题
            orderItemVO.setTitle(item.getSkuName());
            return orderItemVO;
        }).collect(Collectors.toList());
        wareSkuLockVO.setLocks(locks);
        R r = wareFeignService.orderStockLock(wareSkuLockVO);
        if (r.getCode() != 0) {
            throw new NoStockException();
        }
        orderResp.setOrder(orderCreateTO.getOrder());

        // 订单创建成功，向消息队列发送订单数据。
        rabbitTemplate.convertAndSend(ORDER_EXCHANGE, ORDER_DELAY_ROUTING_KEY, orderCreateTO.getOrder());
        return orderResp;
    }

    /**
     * 根据orderSn获取订单信息
     *
     * @param orderSn orderSn
     * @return 订单信息
     */
    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {
        return lambdaQuery().eq(OrderEntity::getOrderSn, orderSn).one();
    }

    /**
     * 关闭过期订单
     *
     * @param order 订单
     */
    @Override
    public void closeDelayOrder(OrderEntity order) {
        OrderEntity orderNew = getById(order.getId());
        // 订单状态为新建状态才能取消订单
        if (Objects.equals(orderNew.getStatus(), CREATE_NEW.getCode())) {
            OrderEntity orderUpdate = new OrderEntity();
            orderUpdate.setId(orderNew.getId());
            // 设置状态为已取消
            orderUpdate.setStatus(CANCELLED.getCode());
            updateById(orderUpdate);
            OrderTO orderTO = new OrderTO();
            BeanUtils.copyProperties(orderNew, orderTO);

            /*
             * 为什么在这里向消息队列发送消息？向哪个队列发送消息？
             * 这里是向解锁库存的消息队列发送消息。
             * 因为存在网络延时等各种原因，在这里已经将订单的状态设置为已取消状态，
             * 但是因为网络延时等原因，没有及时向RabbitMQ没有及时收到确认消息。
             * 这时候其实订单是已经处于取消状态了，但是这时候解锁库存的延时队列到期了，开始执行解锁库存逻辑，
             * 解锁库存逻辑判断订单依然是新建状态，不会去解锁库存，这就造成了订单已经取消但是库存却得不到释放。
             *
             * 因此这里将订单设置为已取消后，直接向解锁库存的队列发送消息，直接执行库存解锁逻辑。
             */
            rabbitTemplate.convertAndSend(ORDER_EXCHANGE, ORDER_RELEASE_OTHER_ROUTING_KEY, orderTO);
        }
    }

    /**
     * 获取当前订单的支付信息
     *
     * @param orderSn orderSn
     * @return 订单支付信息
     */
    @Override
    public PayVO getOrderPay(String orderSn) {
        PayVO payVO = new PayVO();
        OrderEntity order = getOrderByOrderSn(orderSn);
        List<OrderItemEntity> orderItems = orderItemService.listByOrderSn(orderSn);
        // 获取到当前订单中的第一个订单项
        OrderItemEntity orderItem = orderItems.get(0);
        BigDecimal totalAmount = order.getPayAmount().setScale(2, RoundingMode.UP);
        // 设置订单支付金额
        payVO.setTotalAmount(totalAmount.toString());
        // 订单号
        payVO.setOutTradeNo(orderSn);
        // 订单标题，这里设置为第一个订单项的sku标题。
        payVO.setSubject(orderItem.getSkuName());
        // 商品描述，这里设置为第一个订单项的销售属性。
        payVO.setBody(orderItem.getSkuAttrsVals());
        return payVO;
    }

    /**
     * 获取当前用户的订单分页列表
     *
     * @param params 分页参数
     * @return 当前用户订单列表分页参数
     */
    @Override
    public PageUtils listPagesWithItem(Map<String, Object> params) {
        MemberRespVO loginUser = UserHandler.get();

        // 获取当前用户的分页数据并根据订单id倒序排序
        IPage<OrderEntity> page = this.page(new Query<OrderEntity>().getPage(params),
            lambdaQuery().eq(OrderEntity::getMemberId, loginUser.getId()).orderByDesc(OrderEntity::getId).getWrapper());

        List<OrderEntity> orderNew = page.getRecords().stream().peek(order -> {
            List<OrderItemEntity> orderItems = orderItemService.listByOrderSn(order.getOrderSn());
            order.setItems(orderItems);
        }).collect(Collectors.toList());

        page.setRecords(orderNew);
        return new PageUtils(page);
    }

    /**
     * 处理支付宝直接结果<br>
     * 使用支付宝支付后，支付宝以异步方式通知商户进行处理， 商户处理成功后向支付宝返回success。
     *
     * @param payAsyncVO 支付数据
     * @return 处理结果
     */
    @Override
    public String payResultHandler(PayAsyncVO payAsyncVO) {
        PaymentInfoEntity paymentInfo = new PaymentInfoEntity();
        // 支付宝单号
        paymentInfo.setAlipayTradeNo(payAsyncVO.getTrade_no());
        // 订单号
        paymentInfo.setOrderSn(payAsyncVO.getOut_trade_no());
        String tradeStatus = payAsyncVO.getTrade_status();
        // 交易状态
        paymentInfo.setPaymentStatus(tradeStatus);
        // 回调时间
        paymentInfo.setCallbackTime(payAsyncVO.getNotify_time());
        // 保存支付详情
        paymentInfoService.save(paymentInfo);

        // 修改订单状态为已付款
        if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
            baseMapper.updateStatusByOrderSn(payAsyncVO.getOut_trade_no(), PAYED.getCode());
        }
        return "success";
    }

    /**
     * 创建秒杀订单
     *
     * @param seckillOrderTO 秒杀订单数据
     */
    @Override
    public void addSeckillOrder(SeckillOrderTO seckillOrderTO) {
        // 保存订单
        OrderEntity order = new OrderEntity();
        order.setOrderSn(seckillOrderTO.getOrderSn());
        order.setMemberId(seckillOrderTO.getMemberId());
        order.setStatus(CREATE_NEW.getCode());
        BigDecimal payAmount = seckillOrderTO.getSeckillPrice().multiply(new BigDecimal(seckillOrderTO.getNum().toString()));
        order.setPayAmount(payAmount);
        save(order);

        // 保存订单项
        OrderItemEntity orderItem = new OrderItemEntity();
        orderItem.setOrderSn(seckillOrderTO.getOrderSn());
        orderItem.setRealAmount(payAmount);
        orderItem.setSkuQuantity(seckillOrderTO.getNum());
        orderItemService.save(orderItem);
    }

    /**
     * 保存订单
     *
     * @param order 订单数据
     */
    private void saveOrder(OrderCreateTO order) {
        // 保存订单
        OrderEntity orderEntity = order.getOrder();
        orderEntity.setModifyTime(new Date());
        save(orderEntity);

        // 保存订单项
        List<OrderItemEntity> items = order.getItems();
        orderItemService.saveBatch(items);
    }

    /**
     * 构建订单创建TO
     *
     * @return orderCreateTO
     */
    private OrderCreateTO buildOrderCreateTO() {
        OrderCreateTO orderCreateTO = new OrderCreateTO();
        // 订单号
        String orderSn = IdWorker.getTimeId();
        // 根据订单号构建订单
        OrderEntity order = buildOrder(orderSn);
        // 构建订单中的订单项
        List<OrderItemEntity> items = buildOrderItems(orderSn);

        // 订单中有订单项，再计算价格。
        if (items != null) {
            computePrice(order, items);
        }
        orderCreateTO.setOrder(order);
        orderCreateTO.setItems(items);
        return orderCreateTO;
    }

    /**
     * 计算价格与积分
     *
     * @param order 订单
     * @param items 订单项
     */
    private void computePrice(OrderEntity order, List<OrderItemEntity> items) {
        BigDecimal total = new BigDecimal("0.0");
        BigDecimal coupon = new BigDecimal("0.0");
        BigDecimal integration = new BigDecimal("0.0");
        BigDecimal promotion = new BigDecimal("0.0");
        BigDecimal giftGrowth = new BigDecimal("0.0");
        BigDecimal giftIntegration = new BigDecimal("0.0");
        // 将单个订单项中的金额与积分全部累加
        for (OrderItemEntity item : items) {
            coupon = coupon.add(item.getCouponAmount());
            integration = integration.add(item.getIntegrationAmount());
            promotion = promotion.add(item.getPromotionAmount());
            total = total.add(item.getRealAmount());
            giftGrowth = giftGrowth.add(new BigDecimal(item.getGiftGrowth().toString()));
            giftIntegration = giftIntegration.add(new BigDecimal(item.getGiftIntegration().toString()));
        }
        // 设置金额
        order.setTotalAmount(total);
        order.setPayAmount(total.add(order.getFreightAmount()));

        // 设置优惠金额
        order.setPromotionAmount(promotion);
        order.setCouponAmount(coupon);
        order.setIntegrationAmount(integration);

        // 设置积分
        order.setGrowth(giftGrowth.intValue());
        order.setIntegration(giftIntegration.intValue());
    }

    /**
     * 构建订单
     *
     * @param orderSn 订单号
     * @return orderEntity
     */
    private OrderEntity buildOrder(String orderSn) {
        OrderEntity order = new OrderEntity();
        // 订单号
        order.setOrderSn(orderSn);

        // 库存
        OrderSubmitVO submit = orderSubmitThreadLocal.get();
        R r = wareFeignService.getFare(submit.getAddrId());
        FareVO fareVO = r.getData("data", new TypeReference<FareVO>() {});
        order.setFreightAmount(fareVO.getFare());

        // 收货地址
        MemberReceiveAddressDO address = fareVO.getAddress();
        order.setReceiverCity(address.getCity());
        order.setReceiverDetailAddress(address.getDetailAddress());
        order.setReceiverName(address.getName());
        order.setReceiverPhone(address.getPhone());
        order.setReceiverPostCode(address.getPostCode());
        order.setReceiverProvince(address.getProvince());
        order.setReceiverRegion(address.getRegion());

        // 订单状态
        order.setStatus(CREATE_NEW.getCode());
        // 自定确认时间
        order.setAutoConfirmDay(7);
        // 删除状态[0没有删除，1已经删除]
        order.setDeleteStatus(0);
        // 下单用户id
        order.setMemberId(UserHandler.get().getId());

        return order;
    }

    /**
     * 构建所有订单中所有订单项
     *
     * @return 订单项集合
     */
    private List<OrderItemEntity> buildOrderItems(String orderSn) {
        // 获取当前用户的购物车数据，若购物车为空，直接返回空。
        List<OrderItemVO> items = cartFeignService.listCurrentUserCartItems();
        if (items == null || items.size() == 0) {
            return null;
        }
        return items.stream().map(item -> {
            // 构建单个订单项
            OrderItemEntity orderItem = buildOrderItem(item);
            // 设置订单号
            orderItem.setOrderSn(orderSn);
            return orderItem;
        }).collect(Collectors.toList());
    }

    /**
     * 构建订单中单个订单项
     *
     * @param itemVO 订单项参数
     * @return orderItemEntity
     */
    private OrderItemEntity buildOrderItem(OrderItemVO itemVO) {
        OrderItemEntity orderItem = new OrderItemEntity();
        // 设置sku信息
        orderItem.setSkuId(itemVO.getSkuId());
        orderItem.setSkuName(itemVO.getTitle());
        orderItem.setSkuPic(itemVO.getImage());
        orderItem.setSkuPrice(itemVO.getPrice());
        // 将集合转为String，分隔符为;
        String skuAttr = StringUtils.collectionToDelimitedString(itemVO.getSkuAttr(), ";");
        orderItem.setSkuAttrsVals(skuAttr);
        orderItem.setSkuQuantity(itemVO.getCount());

        /*
         * 积分信息
         * 积分默认为：商品当前的价格 × 商品的购买数量
         */
        int integral = itemVO.getPrice().multiply(new BigDecimal(itemVO.getCount().toString())).intValue();
        orderItem.setGiftGrowth(integral);
        orderItem.setGiftIntegration(integral);

        // 设置spu信息
        R r = productFeignService.getSpuInfoBySkuId(itemVO.getSkuId());
        SpuInfoVO spuInfo = r.getData("data", new TypeReference<SpuInfoVO>() {});
        orderItem.setSpuId(spuInfo.getId());
        orderItem.setSpuBrand(spuInfo.getBrandId().toString());
        orderItem.setSpuName(spuInfo.getSpuName());
        orderItem.setCategoryId(spuInfo.getCatalogId());

        // 设置优惠金额
        orderItem.setPromotionAmount(new BigDecimal("0"));
        orderItem.setCouponAmount(new BigDecimal("0"));
        orderItem.setIntegrationAmount(new BigDecimal("0"));

        /*
         * 设置真实金额
         * 真实金额：(商品价格 × 商品数量) - 优惠金额
         */
        BigDecimal oldPrice = orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuQuantity().toString()));
        BigDecimal realPrice = oldPrice.subtract(orderItem.getCouponAmount()).subtract(orderItem.getPromotionAmount())
            .subtract(orderItem.getIntegrationAmount());
        orderItem.setRealAmount(realPrice);

        return orderItem;
    }

}